package cn.gglol.code.generator;

import cn.gglol.code.generator.domain.JvvTable;
import cn.gglol.code.generator.domain.JvvTableColumn;
import cn.gglol.code.generator.utils.*;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import static cn.gglol.code.generator.utils.text.CharsetKit.UTF_8;


/**
 * 生成器
 *
 * @author <a href='Jvvtao:jwtao520@qq.com'>Jvvtao<a/>
 * @since
 */
public class Generator {

    private static DatabaseHelpers databaseHelpers;

    private static ErrorStack errorStack;

    private static void initErrorString() {
        if (errorStack == null)
            errorStack = new ErrorStack();
        else
            errorStack.clear();
    }

    private static void initDatabaseHelpers() {
        if (databaseHelpers == null)
            databaseHelpers = DatabaseHelpers.generate();
    }

    public static void run(String tableNames) {
        if (ObjectUtils.isEmpty(tableNames)) {
            throw new RuntimeException("请输入表名，多表名用个逗号分隔");
        }

        System.out.println("\n\n\n开始生成代码 >>>>");

        initDatabaseHelpers();
        initErrorString();

        List<JvvTable> tables = Arrays.stream(tableNames.split(",")).map(s -> buildTable(s)).filter(t -> t != null).collect(Collectors.toList());

        errorStack.tryThrow();

        if (Config.isOutZip())
            exportFileZip(tables);
        else
            exportFile(tables);

        errorStack.tryThrow();

        System.out.println("\n\n\n开始生成代码 <<<<");
    }

    private static void exportFile(List<JvvTable> tables) {
        VelocityInitializer.initVelocity();

        for (JvvTable table : tables) {
            VelocityContext context = VelocityUtils.prepareContext(table);

            // 获取模板列表
            List<String> templates = VelocityUtils.getTemplateList(Config.isUsePlus());
            for (String template : templates) {
                // 渲染模板
                StringWriter sw = new StringWriter();
                Template tpl = Velocity.getTemplate(template, UTF_8);
                tpl.merge(context, sw);

                try {
                    String path = getGenPath(table, template);
                    FileUtils.writeStringToFile(new File(path), sw.toString(), UTF_8);
                } catch (IOException e) {
                    errorStack.add(String.format("渲染模板失败，表名：%s\t%s", table.getTableName(), e));
                }
            }
        }
    }


    private static void exportFileZip(List<JvvTable> tables) {
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        ZipOutputStream zip = new ZipOutputStream(outputStream);

        VelocityInitializer.initVelocity();

        StringBuilder tableSb = new StringBuilder();
        for (JvvTable table : tables) {
            VelocityContext context = VelocityUtils.prepareContext(table);

            tableSb.append(table.getTableName()).append("_");

            // 获取模板列表
            List<String> templates = VelocityUtils.getTemplateList(Config.isUsePlus());
            for (String template : templates) {
                // 渲染模板
                StringWriter sw = new StringWriter();
                Template tpl = Velocity.getTemplate(template, UTF_8);
                tpl.merge(context, sw);

                try {
                    // 添加到zip
                    zip.putNextEntry(new ZipEntry(VelocityUtils.getFileName(template, table)));
                    IOUtils.write(sw.toString(), zip, UTF_8);
                    IOUtils.closeQuietly(sw);
                    zip.flush();
                    zip.closeEntry();
                } catch (IOException e) {
                    errorStack.add(String.format("渲染模板失败，表名：%s\t%s", table.getTableName(), e));
                }
            }
        }

        IOUtils.closeQuietly(zip);
        byte[] data = outputStream.toByteArray();
        IOUtils.closeQuietly(outputStream);

        if (tableSb.length() > 0) {
            tableSb.deleteCharAt(tableSb.length() - 1).append(".zip");

            try {
                String path = getZipPath(tableSb.toString());
                FileUtils.writeByteArrayToFile(new File(path), data, false);
            } catch (IOException e) {
                errorStack.add(String.format("保存Zip异常，%s", e));
            }
        }
    }

    /**
     * 获取代码生成Zip文件地址
     *
     * @param zipFile 文件名
     * @return
     */
    private static String getZipPath(String zipFile) {
        return new StringBuilder(System.getProperty("user.dir"))
                .append(File.separator).append("src")
                .append(File.separator).append("main")
                .append(File.separator).append("zip")
                .append(File.separator).append(zipFile)
                .toString();
    }

    /**
     * 获取代码生成地址
     *
     * @param table    业务表信息
     * @param template 模板文件路径
     * @return 生成地址
     */
    public static String getGenPath(JvvTable table, String template) {
        return System.getProperty("user.dir") + File.separator + "src" + File.separator + VelocityUtils.getFileName(template, table);
    }

    public static JvvTable buildTable(String tableName) {
        if (tableName == null || tableName.trim().length() == 0) {
            errorStack.add("存在传入表名为空");
            return null;
        }

        JvvTable table = databaseHelpers.getTable(tableName);
        if (table == null) {
            errorStack.add(String.format("%s，表未找到", tableName));
            return null;
        }

        if (StringUtils.isEmpty(table.getTableComment())) {
            errorStack.add(String.format("%s，表注释缺失", tableName));
        }

        GenUtils.initTable(table);
        buildTableColumn(table);
        setPkColumn(table);
        return table;
    }

    private static void buildTableColumn(JvvTable table) {
        List<JvvTableColumn> columns = databaseHelpers.getTableColumn(table.getTableName());
        if (CollectionUtils.isEmpty(columns)) {
            errorStack.add(String.format("%s，未找到表字段", table.getTableName()));
            return;
        }

        table.setColumns(columns);

        List<String> columnNames = new ArrayList<>();
        for (JvvTableColumn column : columns) {
            if (column.getColumnName() == null) {
                errorStack.add(String.format("%s，表字段名为空", table.getTableName()));
            }
            if (!ignoreCommentField(column.getColumnName())
                    && StringUtils.isEmpty(column.getColumnComment())) {
                errorStack.add(String.format("%s.%s，字段注释缺失", table.getTableName(), column.getColumnName()));
            }

            GenUtils.initColumnField(column);
            columnNames.add(column.getColumnName());
        }

        checkFieldMissing(table.getTableName(), columnNames);
    }

    private static void checkFieldMissing(String tableName, List<String> columnNames) {
        if (StringUtils.isNotEmpty(Config.getMustFields())) {
            Arrays.stream(Config.getMustFields().split(",")).forEach(filed -> {
                if (!columnNames.contains(filed)) {
                    errorStack.add(String.format("%s.%s，字段缺失", tableName, filed));
                }
            });
        }
    }

    private static boolean ignoreCommentField(String columnName) {
        if (StringUtils.isNotEmpty(Config.getIgnoreCommentFields())) {
            return Arrays.stream(Config.getIgnoreCommentFields().split(",")).collect(Collectors.toList()).contains(columnName);
        }
        return false;
    }

    public static void setPkColumn(JvvTable table) {
        for (JvvTableColumn column : table.getColumns()) {
            if (column.isPk()) {
                table.setPkColumn(column);
                break;
            }
        }
        if (StringUtils.isNull(table.getPkColumn())) {
            table.setPkColumn(table.getColumns().get(0));
        }
    }
}
